import { newArrayProto } from "./array"
import Dep from "./dep"

class Observe {
  constructor(data) {

    // 当属性时数组和对象,也进行依赖收集,对象是为了$set等方法能够取到dep进行更新视图,数组是为了触发重写的方法更新视图
    this.dep = new Dep()

    data.__ob__ = this  // 每个属性代理后都会有个__ob__指向当前属性的的观察者实例
    // 把this挂到data上, 方便之后直接使用Observe即观察者里的方法, 但是对象会代理所有属性,此时其所有的__ob__属性又指向了自己,所以死循环
    // 可以使用如下方法将属性设为不可遍历
    Object.defineProperty(data, "__ob__", {
      value: this,
      enumerable: false
    })

    // 判断对象和数组,进行分开处理
    if (Array.isArray(data)) {
      // 数组
      this.observeArray(data)  // 如果数组中是对象，那么就可以进行观测，依赖收集
      data.__proto__ = newArrayProto
      // console.log(newArrayProto.__proto__)

    } else {
      // 对象
      this.walk(data)
    }
  }
  walk(data) {
    Object.keys(data).forEach(key => defineReactive(data, key, data[key]))
  }
  observeArray(array) {
    // 这里并没有对array本身进行依赖收集, 也就是说如果观测的data就是一个数组, 此时就不会再到直接defineReactive
    // 而是遍历了所有的元素对元素再次观测，此时如果不是对象，observe就直接renturn，此时不会收集此数组本身的依赖，因而对数组更改不会触发更新
    // 所以在defineReactive方法内，当传入的value是数组的时候，说明是嵌套了数组，此时对这个value进行依赖收集就可以了
    array.forEach(item => {
      observe(item)
    })
  }
}

// 深层次嵌套递归，递归多了性能差，不存在的属性检测不到，要重写方法      vue3 => proxy
export function arrayDepend(array) {
  // 递归：数组的每个元素的依赖收集Watcher
  let ob = array.__ob__
  ob.dep.depend()
  array.forEach(item => {
    if (Array.isArray(item)) {
      arrayDepend(item)
    }
  })
}

export function defineReactive(obj, key, value) { // 闭包
  // 递归, 如果是对象才会new Observe, childOb才有值
  let childOb = observe(value)
  let dep = new Dep()

  // 这里只对对象的属性进行了依赖收集,数组不会走到这里
  // 这里代理的是data对象上的各个属性
  Object.defineProperty(obj, key, {
    get() {
      // debugger
      // 在Watcher实例化的时候，会调用get方法，get方法会调用这里的get方法，
      // 这里的Dep.target就是Watcher实例，这里会进行依赖收集，当计算属性依赖的值发生变化时，会触发对应的依赖进行更新，此时Dep.target就是计算属性的watcher
      if (Dep.target) {
        // 只有渲染组件的时候(即在模板内{{}}取值的时候render生成_s函数才会走入此if分支)
        // 此时进行依赖收集，收集对应的watcher到dep，当属性改变时触发dep存储的watcher进行渲染即可

        // 如果value还是对象或者数组, 那么对对象和数组挂的dep进行依赖收集,这样在数组或对象上的__ob__上就可以拿到收集了wacther的dep了
        // 这里解决了第一次data进来的数组的依赖收集，但是如果数组里面嵌套数组，嵌套内部的数组根本不会走到defineReactive，也就不会进行依赖收集
        // 如果忘了，可以走一遍试试
        childOb && childOb.dep.depend()

        // 解决嵌套数组无法收集依赖的问题，如果第一次data进来的就是数组，对此value直接遍历完，内部所有的属性全部对依赖进行收集Watcher
        // 此后如此对数组内的值进行操作时，也可以通过dep取到收集到的Watcher进行更新
        if (Array.isArray(value)) {
          arrayDepend(value)
        }

        dep.depend() // 收集依赖
      }
      return value
    },
    set(newValue) {
      debugger
      if (value === newValue) return
      // 如果直接修改data里的值为对象还需要再次代理(可能是对象)
      observe(newValue)
      value = newValue
      // notify 设置属性的时候触发所有的watcher的更新视图方法
      dep.notify()
    }
  })
}

// 观测(劫持)对象---
export function observe(data) {
  // null 的类型也是对象
  if (typeof data !== 'object' || data === null) return

  // 复杂属性才观测
  return new Observe(data)
}

// 数组 null 的类型也是对象
// 数组要使用Array.isArray判断
// console.log(typeof null)
// console.log(typeof [])
// console.log(Array.isArray([]))